//
//  FMDBManager.m
//  LBBase
//
//  Created by Gary on 15/7/1.
//  Copyright (c) 2015年 Lebo. All rights reserved.
//

#import "FMDBManager.h"
#import "FMDatabase.h"
#import "CommonDefine.h"
#import "ModuleDefine.h"
#import "Common.h"

#import "BrandModel.h"
#import "BrandSetModel.h"

@implementation FMDBManager

static FMDBManager *sharedInstance;

+ (FMDBManager *)sharedInstance {
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        sharedInstance = [[[self class] alloc] init];
    });
    return sharedInstance;
}

+ (FMDatabase *)openDatabase {
    FMDatabase *db = [FMDatabase databaseWithPath:kDatabaseFilePath];
    if (![db open]) {
        LogInfo(@"open database failed!");
        abort();
        return nil;
    }
    return db;
}

+ (void)recreateTables {
    BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:kDatabaseFilePath];
    if (!isExist) {
        FMDatabase *db = [FMDBManager openDatabase];
        [[FMDBManager sharedInstance] createTables];
        [db close];
    }
}

#pragma mark - 创建表

- (void)createTables {
    [self createTable:[BrandModel class]];
    [self createTable:[BrandSetModel class]];
}

- (void)createTable:(Class)class {
    BOOL ret = [self executeUpdate:[class sql]];
    LogInfo(@"create table %@: %@",  [self getTableName:class], ret ? @"success" : @"fail");
}

#pragma mark - 添加

// 添加
- (BOOL)insert:(BaseModel*)model {
    // 属性名 - 属性值
    NSDictionary *properties = [self propertyDictionaryWithoutAutoKey:model];
    NSString *sql = [self makeInsertSql:model properties:properties];
    return [self executeUpdate:sql values:properties.allValues];
}

// 批量添加
- (BOOL)insert:(NSArray*)models isTransaction:(BOOL)flag {
    NSMutableArray *sqls = [[NSMutableArray alloc] initWithCapacity:models.count];
    NSMutableArray *values = [[NSMutableArray alloc] initWithCapacity:models.count];
    for (BaseModel *model in models) {
        // 属性名 - 属性值
        NSDictionary *properties = [self propertyDictionaryWithoutAutoKey:model];
        NSString *sql = [self makeInsertSql:model properties:properties];
        [sqls addObject:sql];
        NSDictionary *propertyValue = [model dictionaryOfPropertyAndValue];
        NSMutableArray *valueArr = [NSMutableArray array];
        for (NSString *key in properties.allKeys) {
            [valueArr addObject:[propertyValue objectForKey:key]];
        }
        [values addObject:valueArr];
    }
    return [self executeUpdate:sqls values:values isTransaction:flag];
}

#pragma mark - 删除

- (BOOL)deleteT:(BaseModel*)model {
    NSString *sql = [NSString stringWithFormat:@"delete from %@", [self getTableName:model.class]];
    LogInfo(@"delete sql: %@", sql);
    return [self executeUpdate:sql];
}

#pragma mark - 查询

- (NSString*)makeInsertSql:(BaseModel*)model properties:(NSDictionary*)properties {
    // sql语句
    NSMutableString *columnsql = [NSMutableString stringWithString:@""];
    NSMutableString *valuesql = [NSMutableString stringWithString:@""];
    for (NSString *key in properties.allKeys) {
        Property *property = [properties objectForKey:key];
        [columnsql appendFormat:@"%@, ", property.column];
        [valuesql appendString:@"?, "];
    }
    
    if (columnsql.length > 0) {
        NSString *columns = [columnsql substringToIndex:columnsql.length - 2];
        NSString *values = [valuesql substringToIndex:valuesql.length - 2];
        
        // 添加数据的sql语句
        return [NSString stringWithFormat:@"insert into %@ (%@) values (%@)", [self getTableName:model.class], columns, values];
    }
    return nil;
}

- (NSDictionary*)propertyDictionaryWithoutAutoKey:(BaseModel*)model {
    NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:[model.class propertyDictionary]];
    [dic removeObjectForKey:kBaseModelPropertyId];
    return dic;
}

// 数据表名
- (NSString*)getTableName:(Class)model {
    return [model tableName];
}

#pragma mark - execute sql

// 查询功能函数
- (NSArray*)executeQuery:(NSString*)sql class:(Class)class {
    dispatch_async(dispatch_get_main_queue(), ^{
        LogInfo(@"query sql: %@", sql);
    });
    
    // 属性名 - Property
    NSDictionary *propertyDic = [class propertyDictionary];
    FMDatabase *db = [FMDBManager openDatabase];
    FMResultSet *result = [db executeQuery:sql];
    NSMutableArray *arr = [[NSMutableArray alloc] init];
    while ([result next]) {
        BaseModel *model = [[class alloc] init];
        for (NSString *name in propertyDic.allKeys) {
            Property *property = [propertyDic objectForKey:name];
            switch (property.type) {
                case PDTBOOL: {
                    BOOL value = [result boolForColumn:property.column];
                    [model setValue:[NSNumber numberWithBool:value] forKey:name];
                    break;
                }
                case PDTInt: {
                    int value = [result intForColumn:property.column];
                    [model setValue:[NSNumber numberWithInt:value] forKey:name];
                    break;
                }
                case PDTLong: {
                    long value = [result longForColumn:property.column];
                    [model setValue:[NSNumber numberWithLong:value] forKey:name];
                    break;
                }
                case PDTLongLong: {
                    long long int value = [result longLongIntForColumn:property.column];
                    [model setValue:[NSNumber numberWithLongLong:value] forKey:name];
                    break;
                }
                case PDTUnsignedLongLong: {
                    unsigned long long int value = [result unsignedLongLongIntForColumn:property.column];
                    [model setValue:[NSNumber numberWithUnsignedLongLong:value] forKey:name];
                    break;
                }
                case PDTDouble: {
                    double value = [result doubleForColumn:property.column];
                    [model setValue:[NSNumber numberWithDouble:value] forKey:name];
                    break;
                }
                case PDTDate: {
                    NSDate *value = [result dateForColumn:property.column];
                    [model setValue:value forKey:name];
                    break;
                }
                case PDTString: {
                    NSString *value = [result stringForColumn:property.column];
                    [model setValue:value forKey:name];
                    break;
                }
                case PDTUTF8String: {
                    const char *psz = (const char *)[result UTF8StringForColumnName:property.column];
                    NSString *value = [NSString stringWithUTF8String:psz];
                    [model setValue:value forKey:name];
                    
                    break;
                }
                case PDTObject: {
                    id value = [result objectForColumnName:property.column];
                    [model setValue:value forKey:name];
                    break;
                }
                default: {
                    LogInfo(@"unrecognized data type!");
                    break;
                }
            }
        }
        [arr addObject:model];
    }
    
    [db close];
    return [NSArray arrayWithArray:arr];
}

- (BOOL)executeUpdate:(NSString*)sql, ... {
    dispatch_async(dispatch_get_main_queue(), ^{
        LogInfo(@"execute sql: %@", sql);
    });
    FMDatabase *db = [FMDBManager openDatabase];
    va_list args;
    va_start(args, sql);
    BOOL result = [db executeUpdate:sql withVAList:args];
    va_end(args);
    [db close];
    
    return result;
}

- (BOOL)executeUpdate:(NSString*)sql values:(NSArray*)array {
    dispatch_async(dispatch_get_main_queue(), ^{
        LogInfo(@"execute sql: %@", sql);
    });
    FMDatabase *db = [FMDBManager openDatabase];
    BOOL result = [db executeUpdate:sql withArgumentsInArray:array];
    [db close];
    return result;
}

- (BOOL)executeUpdate:(NSArray*)sqls values:(NSArray*)values isTransaction:(BOOL)flag {
    dispatch_async(dispatch_get_main_queue(), ^{
        LogInfo(@"executeUpdate sqls: %@", sqls);
    });
    if (flag) {
        FMDatabase *db = [FMDBManager openDatabase];
        [db beginTransaction];
        BOOL rollBack = NO;
        BOOL ret = YES;
        @try {
            NSInteger count = sqls.count;
            for (int i = 0; i < count; i++) {
                NSString *sql = [sqls objectAtIndex:i];
                NSArray *value = values.count > i ? [values objectAtIndex:i] : nil;
                ret &= [db executeUpdate:sql withArgumentsInArray:value];
                if (!ret) {
                    @throw ([NSException exceptionWithName:kEmptyString
                                                    reason:[NSString stringWithFormat:@"execute sql fail: %@ %@", sql, value]
                                                  userInfo:nil]);
                }
            }
        }
        @catch (NSException *exception) {
            dispatch_async(dispatch_get_main_queue(), ^{
                LogError(@"%@", exception.reason);
            });
            rollBack = YES;
            [db rollback];
            //[db close];
            ret = NO;
        }
        @finally {
            if (!rollBack) {
                [db commit];
            } else {
                ret = NO;
            }
            //[db close];
        }
        return ret;
    } else {
        FMDatabase *db = [FMDBManager openDatabase];
        NSInteger count = sqls.count;
        BOOL ret = YES;
        for (int i = 0; i < count; i++) {
            NSString *sql = [sqls objectAtIndex:i];
            NSArray *value = values.count > i ? [values objectAtIndex:i] : nil;
            ret &= [db executeUpdate:sql withArgumentsInArray:value];
        }
        [db close];
        return ret;
    }
}

@end
